#![cfg(target_vendor = "apple")]

pub extern crate xloop_types as types;

#[allow(non_snake_case)]
pub mod CABase;
#[allow(non_snake_case)]
pub mod MTKView;
pub mod app;
pub mod error;
pub mod win;

pub mod prelude {}

#[derive(Debug)]
pub struct Application;
impl types::App for Application {
	type Error = error::Error;
	type Ref<'a> = app::AppRef<'a>;
	fn with(fun: impl FnOnce(Self::Ref<'_>)) {
		app::with_app(fun)
	}
	fn run(rep: impl types::AppRep<Self>) -> Result<(), Self::Error> {
		use core::{ffi, ptr};
		use objc2::ClassType as _;
		use objc2_foundation::ns_string;
		use objc2_foundation::NSString;

		app::init_app(rep);

		let _ = app::AppDelegate::class();
		extern "C" {
			pub fn UIApplicationMain(
				argc: ffi::c_int,
				argv: *const ffi::c_char,
				principalClassName: Option<&NSString>,
				delegateClassName: Option<&NSString>,
			) -> ffi::c_int;
		}
		unsafe {
			UIApplicationMain(
				0, //
				ptr::null(),
				None,
				Some(ns_string!(app::AppDelegate::NAME)),
			)
		};
		Ok(())
	}
}
impl types::Log for Application {
	fn log(level: types::LevelFilter) {
		let level = match level {
			types::LevelFilter::Off => log::LevelFilter::Off,
			types::LevelFilter::Error => log::LevelFilter::Error,
			types::LevelFilter::Warn => log::LevelFilter::Warn,
			types::LevelFilter::Info => log::LevelFilter::Info,
			types::LevelFilter::Debug => log::LevelFilter::Debug,
			types::LevelFilter::Trace => log::LevelFilter::Trace,
		};
		env_logger::builder().filter_level(level).init();
	}
}
impl types::Fs for Application {
	fn read(path: impl AsRef<std::path::Path>) -> impl std::future::Future<Output = std::io::Result<Vec<u8>>> {
		thread_local! {
			static MAIN_BUNDLE:std::cell::OnceCell<Option<std::path::PathBuf>> = std::cell::OnceCell::new();
		}
		MAIN_BUNDLE.with(|v| {
			let root = v.get_or_init(|| {
				let root = unsafe { objc2_foundation::NSBundle::mainBundle().resourcePath() };
				root.map(|v| std::path::PathBuf::from(v.to_string()))
			});
			if let Some(root) = root {
				let file = root.join(path.as_ref());
				ReadFuture(Some(file))
			} else {
				ReadFuture(None)
			}
		})
	}
}
pub struct ReadFuture(Option<std::path::PathBuf>);
impl std::future::Future for ReadFuture {
	type Output = std::io::Result<Vec<u8>>;
	fn poll(self: core::pin::Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
		if let Some(file) = &self.0 {
			std::task::Poll::Ready(std::fs::read(file))
		} else {
			std::task::Poll::Ready(Err(std::io::Error::new(std::io::ErrorKind::NotFound, "no app path")))
		}
	}
}

#[derive(Debug)]
pub struct Window;

impl types::Win for Window {
	type Error = error::Error;
	type WinRaw = win::WinRaw;
	type WinRef<'a> = win::WinRef<'a>;
	type WinHandle = win::WinHandle;
	type Mouse<'a> = win::WinEvent<'a>;
	type Wheel<'a> = win::WinEvent<'a>;
	type Keyboard<'a> = win::WinKeyboard<'a>;
	type Touch<'a> = win::WinTouch<'a>;
	type Ime<'a> = &'a types::Ime;
	type Attr = types::XwinAttr;
	fn win(key: impl AsRef<str>, _: Self::Attr, rep: impl types::WinRep<Self> + 'static) -> Result<Self::WinHandle, Self::Error> {
		let window = win::XLoopWindow::new(key, rep).ok_or(error::Error)?;
		Ok(win::WinHandle(window))
	}
}
